import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core';
import { createFastMutation } from '@yaakapp/app/hooks/useFastMutation';
import { queryClient } from '@yaakapp/app/lib/queryClient';
import { useMemo } from 'react';
import { GitCommit, GitRemote, GitStatusSummary, PullResult, PushResult } from './bindings/gen_git';

export * from './bindings/gen_git';

export interface GitCredentials {
  username: string;
  password: string;
}

export interface GitCallbacks {
  addRemote: () => Promise<GitRemote | null>;
  promptCredentials: (
    result: Extract<PushResult, { type: 'needs_credentials' }>,
  ) => Promise<GitCredentials | null>;
}

const onSuccess = () => queryClient.invalidateQueries({ queryKey: ['git'] });

export function useGit(dir: string, callbacks: GitCallbacks) {
  const mutations = useMemo(() => gitMutations(dir, callbacks), [dir, callbacks]);
  return [
    {
      remotes: useQuery<GitRemote[], string>({
        queryKey: ['git', 'remotes', dir],
        queryFn: () => getRemotes(dir),
      }),
      log: useQuery<GitCommit[], string>({
        queryKey: ['git', 'log', dir],
        queryFn: () => invoke('plugin:yaak-git|log', { dir }),
      }),
      status: useQuery<GitStatusSummary, string>({
        refetchOnMount: true,
        queryKey: ['git', 'status', dir],
        queryFn: () => invoke('plugin:yaak-git|status', { dir }),
      }),
    },
    mutations,
  ] as const;
}

export const gitMutations = (dir: string, callbacks: GitCallbacks) => {
  const push = async () => {
    const remotes = await getRemotes(dir);
    if (remotes.length === 0) {
      const remote = await callbacks.addRemote();
      if (remote == null) throw new Error('No remote found');
    }

    const result = await invoke<PushResult>('plugin:yaak-git|push', { dir });
    if (result.type !== 'needs_credentials') return result;

    // Needs credentials, prompt for them
    const creds = await callbacks.promptCredentials(result);
    if (creds == null) throw new Error('Canceled');

    await invoke('plugin:yaak-git|add_credential', {
      dir,
      remoteUrl: result.url,
      username: creds.username,
      password: creds.password,
    });

    // Push again
    return invoke<PushResult>('plugin:yaak-git|push', { dir });
  };

  return {
    init: createFastMutation<void, string, void>({
      mutationKey: ['git', 'init'],
      mutationFn: () => invoke('plugin:yaak-git|initialize', { dir }),
      onSuccess,
    }),
    add: createFastMutation<void, string, { relaPaths: string[] }>({
      mutationKey: ['git', 'add', dir],
      mutationFn: (args) => invoke('plugin:yaak-git|add', { dir, ...args }),
      onSuccess,
    }),
    addRemote: createFastMutation<GitRemote, string, GitRemote>({
      mutationKey: ['git', 'add-remote'],
      mutationFn: (args) => invoke('plugin:yaak-git|add_remote', { dir, ...args }),
      onSuccess,
    }),
    rmRemote: createFastMutation<void, string, { name: string }>({
      mutationKey: ['git', 'rm-remote', dir],
      mutationFn: (args) => invoke('plugin:yaak-git|rm_remote', { dir, ...args }),
      onSuccess,
    }),
    branch: createFastMutation<void, string, { branch: string }>({
      mutationKey: ['git', 'branch', dir],
      mutationFn: (args) => invoke('plugin:yaak-git|branch', { dir, ...args }),
      onSuccess,
    }),
    mergeBranch: createFastMutation<void, string, { branch: string; force: boolean }>({
      mutationKey: ['git', 'merge', dir],
      mutationFn: (args) => invoke('plugin:yaak-git|merge_branch', { dir, ...args }),
      onSuccess,
    }),
    deleteBranch: createFastMutation<void, string, { branch: string }>({
      mutationKey: ['git', 'delete-branch', dir],
      mutationFn: (args) => invoke('plugin:yaak-git|delete_branch', { dir, ...args }),
      onSuccess,
    }),
    checkout: createFastMutation<string, string, { branch: string; force: boolean }>({
      mutationKey: ['git', 'checkout', dir],
      mutationFn: (args) => invoke('plugin:yaak-git|checkout', { dir, ...args }),
      onSuccess,
    }),
    commit: createFastMutation<void, string, { message: string }>({
      mutationKey: ['git', 'commit', dir],
      mutationFn: (args) => invoke('plugin:yaak-git|commit', { dir, ...args }),
      onSuccess,
    }),
    commitAndPush: createFastMutation<PushResult, string, { message: string }>({
      mutationKey: ['git', 'commit_push', dir],
      mutationFn: async (args) => {
        await invoke('plugin:yaak-git|commit', { dir, ...args });
        return push();
      },
      onSuccess,
    }),
    fetchAll: createFastMutation<string, string, void>({
      mutationKey: ['git', 'checkout', dir],
      mutationFn: () => invoke('plugin:yaak-git|fetch_all', { dir }),
      onSuccess,
    }),
    push: createFastMutation<PushResult, string, void>({
      mutationKey: ['git', 'push', dir],
      mutationFn: push,
      onSuccess,
    }),
    pull: createFastMutation<PullResult, string, void>({
      mutationKey: ['git', 'pull', dir],
      async mutationFn() {
        const result = await invoke<PullResult>('plugin:yaak-git|pull', { dir });
        if (result.type !== 'needs_credentials') return result;

        // Needs credentials, prompt for them
        const creds = await callbacks.promptCredentials(result);
        if (creds == null) throw new Error('Canceled');

        await invoke('plugin:yaak-git|add_credential', {
          dir,
          remoteUrl: result.url,
          username: creds.username,
          password: creds.password,
        });

        // Pull again
        return invoke<PullResult>('plugin:yaak-git|pull', { dir });
      },
      onSuccess,
    }),
    unstage: createFastMutation<void, string, { relaPaths: string[] }>({
      mutationKey: ['git', 'unstage', dir],
      mutationFn: (args) => invoke('plugin:yaak-git|unstage', { dir, ...args }),
      onSuccess,
    }),
  } as const;
};

async function getRemotes(dir: string) {
  return invoke<GitRemote[]>('plugin:yaak-git|remotes', { dir });
}
